home *** CD-ROM | disk | FTP | other *** search
/ Developer CD Series 1996 April: Mac OS SDK / Dev.CD Apr 96 SDK / Dev.CD Apr 96 SDK1.toast / Development Kits (Disc 1) / OpenDoc / Documentation / Tech Notes & Articles / Recipes / UI & Events / Shared Utility Windows < prev    next >
Encoding:
Text File  |  1995-11-07  |  12.9 KB  |  408 lines  |  [TEXT/ttxt]

  1. OpenDoc™ Recipes
  2.  
  3. Shared Utility Windows
  4. By The OpenDoc Design Team
  5. November, 1995
  6.  
  7.  
  8. © 1993-1995  Apple Computer, Inc. All Rights Reserved.
  9. Apple, the Apple logo, and Macintosh are registered trademarks of Apple Computer, Inc.
  10. Mac and OpenDoc are trademarks of Apple Computer, Inc. 
  11.  
  12.  
  13. Introduction
  14.  
  15. The OpenDoc Human Interface Guidelines specify that when an active frame is deactivated, palettes, modeless dialogs and other utility windows should be hidden. When the frame is next activated, these windows should appear in the same position. Furthermore, when the newly activated frame belongs to a different part bound to the same editor, it is desirable that the windows remain shown throughout, rather than disappearing and reappearing immediately. Naturally the contents of these windows must be updated to reflect the state of the newly active frame.
  16.  
  17. One way to achieve these goals is to create window objects which are shared by all instances of a given part type. This can be done using per-process global variables in the editor library. This has the additional benefit of reducing memory requirements. There is other information which can be shared by multiple part instances. The menu bar is one possibilty. Another is the various tokenized strings for foci, presentation types and view types.
  18.  
  19. Implementing shared windows is a little tricky. The technique is described in this document, and demonstrated effectively in the Test Clock part, in which the Display Settings and Alarm Settings dialogs are shared between multiple clock parts.
  20.  
  21.  The key features of the implementation are:
  22.  
  23. A reference-counted C++ object to hold global data, in this case an ODWindow.
  24. Hiding the window, rather than closing it.
  25. The use of the ODFrame method ChangePart().
  26. Using the presentation type to detect relinquishing focus to a frame of the same type
  27.  
  28. Storing Global Data
  29.  
  30. The file ClockGbl.h defines the following class:
  31.  
  32. class ClockGlobals
  33. {
  34. public:
  35.     ClockGlobals();
  36.     ~ClockGlobals();
  37.     void InitClockGlobals(Environment* ev, ODSession* session, ODPart* part);
  38.     void AcquireGlobals(Environment* ev);
  39.     void ReleaseGlobals(Environment* ev);
  40.     void FreeGlobals(Environment* ev);
  41.     …
  42.  
  43. private:        
  44.     short fRefCount;
  45.     ODSession* fSession;
  46.     ODMenuBar* fMenuBar;
  47.     
  48.     ODID         fDisplaySettingsWindow;
  49.     DialogPtr   fDispaySettingsDialog;
  50.     ODTypeToken fFrameView;                
  51.     ODTypeToken fDisplaySettingsPresentation;    
  52.     OrderedCollection*    fDialogFrames;
  53. };
  54.  
  55. and a global variable:
  56.  
  57. extern ClockGlobals* gClockGlobals;
  58.  
  59. Note: It is obviously not absolutely necessary to group all the globals in a single object like this, but it's convenient.
  60.  
  61. The global variable is initialized to NULL  in the implementation file:
  62.  
  63. ClockGlobals* gClockGlobals = kODNULL;
  64.  
  65. The common initialization code called by InitPart() and InitPartFromStorage() contains the following:
  66.  
  67.     if (gClockGlobals)
  68.         gClockGlobals->AcquireGlobals(ev);
  69.     else
  70.     {
  71.         gClockGlobals = new ClockGlobals();
  72.         gClockGlobals->InitClockGlobals(ev, session, partWrapper);
  73.         gClockGlobals->AcquireGlobals(ev);
  74.     }
  75.  
  76. and the part's ReleaseAll() method releases the globals obect:
  77.  
  78.     if (gClockGlobals)
  79.         gClockGlobals->ReleaseGlobals(ev);
  80.  
  81.  The AcquireGlobals() and ReleaseGlobals() methods adjust the ref count of this object, and delete it when the ref count goes to 0, which occurs when the last part bound to the editor is destroyed.
  82.  
  83. void ClockGlobals::AcquireGlobals(Environment* ev)
  84. {
  85.     fRefCount++;
  86. }
  87.  
  88. void ClockGlobals::ReleaseGlobals(Environment* ev)
  89. {
  90.     fRefCount--;
  91.     if (fRefCount == 0)
  92.         this->FreeGlobals(ev);
  93. }
  94.  
  95. void ClockGlobals::FreeGlobals(Environment* ev)
  96. {
  97.     ODWindow* window = fSession->GetWindowState(ev)->AcquireWindow(ev, fDisplaySettingsWindow);
  98.     if (window)
  99.     {
  100.         window->CloseAndRemove(ev);
  101.     }
  102.     if (fDispaySettingsDialog)
  103.     {
  104.         CloseDialog(fDispaySettingsDialog);
  105.         ODDisposePtr(fDispaySettingsDialog);        
  106.     }
  107.  
  108.     ODDeleteObject(fDialogFrames);
  109.     gClockGlobals = kODNULL;
  110.     delete this;
  111. }
  112.  
  113. Opening The Window
  114.  
  115. When the shared window is first opened it is created in the usual way. In the Clock part, the source frame of the dialog window is set to the frame that was active when the dialog was invoked. When the user closes the window, the clock part simply hides it. When the window is subsequently opened, the hidden window is simply shown.
  116. The following method is called when the menu item which selects the dialog is chosen:
  117.  
  118. void ClockGlobals::OpenDisplaySettingsDialog(Environment* ev,  ODFrame* sourceFrame)
  119. {
  120.     ODPart* sourcePart = sourceFrame->AcquirePart(ev);
  121.     ODWindow* window = fSession->GetWindowState(ev)->AcquireWindow(ev, fDisplaySettingsWindow);
  122.     if (window)
  123.     {
  124.         window->Show(ev);
  125.         window->Select(ev);
  126.         ODReleaseObject(ev, window);
  127.     }
  128.     else
  129.     {
  130.         this->CreateDisplaySettingsDialog(ev, sourceFrame);
  131.         this->OpenDisplaySettingsDialog(ev, sourceFrame); // Ooh. Sneaky
  132.     }    
  133.     ODReleaseObject(ev, sourcePart);
  134. }
  135.  
  136. void ClockGlobals::CreateDisplaySettingsDialog(Environment* ev, ODFrame* sourceFrame)
  137. {
  138.     ODSLong savedRefNum;
  139.     ODWindow* settingsWindow = kODNULL;
  140.     ODPart* sourcePart = sourceFrame->AcquirePart(ev);
  141.     
  142.     BeginUsingLibraryResources(savedRefNum);
  143.     fDispaySettingsDialog = GetNewDialog(kClock_DisplaySettingsDialogID,                                                 (Ptr)ODNewPtr(sizeof(DialogRecord)), 
  144.                         (WindowPtr)-1L);
  145.     EndUsingLibraryResources(savedRefNum);
  146.     
  147.     settingsWindow = fSession->GetWindowState(ev)->
  148.                         RegisterWindow(ev, fDispaySettingsDialog, 
  149.                             kODNonPersistentFrameObject,
  150.                              kODFalse,    // Keeps draft open
  151.                              kODFalse,    // Is resizable
  152.                              kODFalse,    // Is floating
  153.                              kODFalse,    // don't save
  154.                              kODFalse,    // don't dispose
  155.                              sourcePart, 
  156.                              fFrameView, // View Type
  157.                              fDisplaySettingsPresentation, // Presentation
  158.                              sourceFrame);
  159.  
  160.     
  161.     if (fDispaySettingsDialog && settingsWindow)
  162.     {
  163.         settingsWindow->Open(ev);
  164.         fDisplaySettingsWindow = settingsWindow->GetID(ev);
  165.         ODReleaseObject(ev, settingsWindow);
  166.     }
  167.     ODReleaseObject(ev, sourcePart);
  168. }
  169.  
  170. The following method is called when the clock part intecepts a window event in the close box. 
  171.  
  172. ODBoolean ClockPart::CloseWindow(Environment* ev, ODFrame* frame)
  173. {
  174.     ODBoolean         handled            = kODFalse;
  175.     ODTypeToken presentation = frame->GetPresentation(ev);
  176.     
  177.     if (presentation == fDisplaySettingsPresentation)
  178.     {
  179.         gClockGlobals->CloseDisplaySettingsWindow(ev);
  180.         handled = kODTrue;
  181.     }
  182.     else if (presentation == fAlarmSettingsPresentation)
  183.     {
  184.         gClockGlobals->CloseAlarmSettingsWindow(ev);
  185.         handled = kODTrue;
  186.     }
  187.         
  188.     return handled;
  189. }
  190.  
  191. void ClockGlobals::CloseDisplaySettingsWindow(Environment* ev)
  192. {
  193.     ODWindow* window = fSession->GetWindowState(ev)->AcquireWindow(ev, fDisplaySettingsWindow);
  194.     if (window)
  195.     {
  196.         window->Hide(ev);
  197.     }
  198.     ODReleaseObject(ev, window);
  199. }
  200.  
  201. Losing  Focus
  202.  
  203. When the user clicks outside a frame of a clock part, the shared dialog window should be hidden, except when the user clicks in a frame belonging to another clock part, in which case the dialog needs to be switched to belong to that part.
  204.  
  205. When CommitRelinquishFocus() is called, the Clock part calls its LostSelectionFocus()  method, which detects that the frame that requested the focus is one of the same type.  This is done here by checking the presentation.  If the focus is switching to another part, the shared dialog window is hidden, otherwise we do nothing and wait for the dialog to be switched when the other part acquires focus.
  206.  
  207. Issue: There may be other  ways of testing for the same editor, but this seems reasonable.
  208.  
  209. void ClockFrame::LostSelectionFocus(Environment* ev, ODFrame* proposedFrame)
  210. {
  211.     ODBoolean samePart = 
  212.         (proposedFrame && (proposedFrame->GetPresentation(ev) == fClockPart->fTimePresentation));
  213.     if (!samePart)
  214.         gClockGlobals->SuspendWindows(ev, kODFalse);
  215. }
  216.  
  217.  
  218. void ClockGlobals::SuspendWindows(Environment* ev, ODBoolean processChange)
  219. {
  220.     OrderedCollectionIterator iter(fDialogFrames);
  221.     
  222.     for (ODFrame* aFrame = (ODFrame*)iter.First(); 
  223.                             iter.IsNotComplete();
  224.                             aFrame = (ODFrame*)iter.Next())
  225.     {
  226.         ClockFrame* clockFrame = (ClockFrame*) aFrame->GetPartInfo(ev);
  227.         if (processChange)
  228.             clockFrame->SuspendProcess(ev);
  229.         else
  230.             clockFrame->SuspendFocus(ev);
  231.     }
  232.  
  233. }
  234.  
  235. void ClockFrame::SuspendFocus(Environment* ev)
  236. {
  237.     if (fFrame->IsRoot(ev) && fShouldHideOnSuspend)
  238.     {    
  239.         TempODWindow window = fFrame->AcquireWindow(ev);
  240.         if (window->IsShown(ev))
  241.         {
  242.             window->Hide(ev);
  243.             fShowWindowOnFocus = kODTrue;
  244.         }
  245.         else
  246.         {
  247.             fShowWindowOnFocus = kODFalse;
  248.         }
  249.     }
  250. }
  251.  
  252. Acquiring Focus
  253.  
  254. When the activating frame acquires the focus, it first calls a method of the global object to retarget the shared dialog windows.  It then tells the global object to show dialogs that may have been hidden.
  255.  
  256. void ClockFrame::AcquiredSelectionFocus(Environment* ev)
  257. {
  258.     gClockGlobals->AcquiringFocus(ev, fFrame);
  259.     gClockGlobals->ResumeWindows(ev, kODFalse);
  260. }
  261.  
  262. Retargeting the dialog is done by calling the ODFrame method ChangePart() and the ODWindow  method SetSourceFrame(). ChangePart removes all facets, detaches the frame from the old part using  DisplayFrameRemoved, the attaches it to the new part using DisplayFrameAdded, and adds the facets back.
  263.  
  264. void ClockGlobals::AcquiringFocus(Environment* ev, ODFrame* activatingFrame)
  265. {
  266.     ODWindow* settingsWindow = fSession->GetWindowState(ev)->AcquireWindow(ev, fDisplaySettingsWindow);
  267.     if (settingsWindow)
  268.     {
  269.         ODPart* activatingPart = activatingFrame->AcquirePart(ev);
  270.         settingsWindow->GetRootFrame(ev)->ChangePart(ev, activatingPart);
  271.         settingsWindow->SetSourceFrame(ev, activatingFrame);
  272.         ODReleaseObject(ev, activatingPart);
  273.         
  274.     }
  275.     ODReleaseObject(ev, settingsWindow);
  276. }
  277.  
  278. void ClockGlobals::ResumeWindows(Environment* ev, ODBoolean processChange)
  279. {
  280.     OrderedCollectionIterator iter(fDialogFrames);
  281.     
  282.     for (ODFrame* aFrame = (ODFrame*)iter.First(); 
  283.                             iter.IsNotComplete();
  284.                             aFrame = (ODFrame*)iter.Next())
  285.     {
  286.         ClockFrame* clockFrame = (ClockFrame*) aFrame->GetPartInfo(ev);
  287.         if (processChange)
  288.             clockFrame->ResumeProcess(ev);
  289.         else
  290.             clockFrame->ResumeFocus(ev);
  291.     }
  292. }
  293.  
  294. void ClockFrame::ResumeFocus(Environment* ev)
  295. {
  296.     if (fFrame->IsRoot(ev) && fShouldHideOnSuspend)
  297.     {    
  298.         TempODWindow window = fFrame->AcquireWindow(ev);
  299.         if (fShowWindowOnFocus && !fInBackground)
  300.         {
  301.             fShowWindowOnFocus = kODFalse; 
  302.             window->Show(ev);
  303.         }
  304.     }
  305. }
  306.  
  307. Loose Ends
  308.  
  309. It can be quite tricky hiding  and showing dialogs correctly in response to both focus changes within a document, and process-switching between documents. The approach in Test Clock, which delegates to frame classes and separates the two cases, seemed to work well.  The ClockFrame::SuspendFocus and ResumeFocus methods were shown above. The SuspendProcess and ResumeProcess methods below are called in response to suspend/resume events:
  310.  
  311.         case kSuspendResumeMessage:
  312.             {
  313.                 ClockFrame* clockFrame = (ClockFrame*) frame->GetPartInfo(ev);
  314.                 const short kResumeMask = 0x01;    // High byte suspend/resume event 
  315.                 ODBoolean    goingToBackground = (event->message & kResumeMask) == 0;
  316.                 
  317.                 if (goingToBackground)
  318.                 {
  319.                     clockFrame->SuspendProcess(ev);    
  320.                 }
  321.                 else
  322.                 {
  323.                     clockFrame->ResumeProcess(ev);    
  324.                 }
  325.  
  326. void ClockFrame::SuspendProcess(Environment* ev)
  327. {
  328.     fInBackground = kODTrue;
  329.     if (fFrame->IsRoot(ev) && fShouldHideOnSuspend)
  330.     {    
  331.         TempODWindow window = fFrame->AcquireWindow(ev);
  332.         if (window->IsShown(ev))
  333.         {
  334.             fShowWindowOnResume = kODTrue;
  335.             window->Hide(ev);
  336.         }
  337.         else
  338.         {
  339.             fShowWindowOnResume = kODFalse; 
  340.         }
  341.     }
  342. }
  343.  
  344. void ClockFrame::ResumeProcess(Environment* ev)
  345. {
  346.     fInBackground = kODFalse;
  347.     if (fFrame->IsRoot(ev) && fShouldHideOnSuspend)
  348.     {    
  349.         TempODWindow window = fFrame->AcquireWindow(ev);
  350.         if (fShowWindowOnResume)
  351.         {
  352.             fShowWindowOnResume = kODFalse; // It may be hidden by user before next Suspend
  353.             fShowWindowOnFocus = kODFalse;
  354.             window->Show(ev);
  355.         }
  356.     }
  357. }
  358.  
  359. Finally, it is necessary to adjust the dialog contents at various points. The Clock part does this  when the dialog frame is first creates, and when the dialog frame is added to a part (which also occurs when switching parts).
  360.  
  361. void ClockDisplaySettingsDialogFrame::Add(Environment* ev)
  362. {
  363.     ClockDialogFrame::Add(ev);
  364.  
  365.     fClockPart->AdjustDialogs(ev);
  366. }
  367.  
  368. The dialog is also adjusted when the user changes from analog to digital display. Since the Clock part is now scriptable and recordable, this is done in the SetData accessor function:
  369.  
  370. void ClockPropAccessor::SetData(AEDesc* data)
  371. {
  372.     …
  373.     case pDisplayMode:
  374.     {
  375.         DescType        theMode;
  376.         ODBoolean        isAnalog;
  377.             
  378.         theMode = **(DescType**)data->dataHandle;
  379.         switch (theMode) 
  380.         {
  381.             case kAEAnalogClock:        isAnalog = kODTrue; break;
  382.             case kAEDigitalClock:        isAnalog = kODFalse; break;
  383.             default:
  384.                 THROW(paramErr);
  385.         }
  386.         fClock->SetAnalog(ev, isAnalog);
  387.         fClock->AdjustDisplayFrames(ev);
  388.         fClock->AdjustDialogs(ev);
  389.     …
  390.  
  391. void ClockPart::AdjustDialogs(Environment* ev)
  392. {
  393.     gClockGlobals->AdjustDialogs(ev);
  394. }
  395.  
  396. void ClockGlobals::AdjustDialogs(Environment* ev)
  397. {
  398.     OrderedCollectionIterator iter(fDialogFrames);
  399.     
  400.     for (ODFrame* aFrame = (ODFrame*)iter.First(); 
  401.                             iter.IsNotComplete();
  402.                             aFrame = (ODFrame*)iter.Next())
  403.     {
  404.         ClockFrame* clockFrame = (ClockFrame*) aFrame->GetPartInfo(ev);
  405.         clockFrame->Adjust(ev);
  406.     }
  407. }
  408.